#include "rendPlatform.hpp"
#include "rendVectorArray.hpp"
#include "rendVect.hpp"
#include "rendMeshUtils.hpp"

#include <string>

#include "rendCameraVisual.hpp"

namespace rend
{

const std::string CameraVisual::s_idx[2] =
{
	std::string("+cam_idx0"),
	std::string("+cam_idx1")
};

static const std::string attr_semantics_position("Vertex");
static const std::string attr_semantics_index("index");


CameraVisual::CameraVisual()
{
	static unsigned i = 0;
	char buffer[16];
	sprintf(buffer, "%u", i++);

	m_vtx = std::string("+cam_vtx_") + buffer;
}


void CameraVisual::buildFrom(const Camera& cam)
{
	VectorArray< float, 3 > vtx(8);

	vect3& v0 = vect3::cast(vtx.get(0));
	vect3& v1 = vect3::cast(vtx.get(2));
	vect3& v2 = vect3::cast(vtx.get(4));
	vect3& v3 = vect3::cast(vtx.get(6));
	vect3& v4 = vect3::cast(vtx.get(1));
	vect3& v5 = vect3::cast(vtx.get(3));
	vect3& v6 = vect3::cast(vtx.get(5));
	vect3& v7 = vect3::cast(vtx.get(7));

	const float extX = cam.getExtentX();
	const float extY = cam.getExtentY();
	const float near = cam.getNearClipDistance();
	const float far	 = cam.getFarClipDistance();

	// near clipping plane
	v0 = vect3(-extX,  extY, -near); // upper left
	v1 = vect3( extX,  extY, -near); // upper right
	v2 = vect3( extX, -extY, -near); // lower right
	v3 = vect3(-extX, -extY, -near); // lower left

	// far clipping plane
	if (cam.getProjectionType() == Camera::PROJECTION_PERSPECTIVE)
	{
		const float r = cam.getFarClipDistance() / cam.getNearClipDistance();

		v4.mul(v0, r); // upper left
		v5.mul(v1, r); // upper right
		v6.mul(v2, r); // lower right
		v7.mul(v3, r); // lower left
	}
	else // cam.getProjectionType() == Camera::PROJECTION_ORTHOGRAPHIC
	{
		v4 = vect3(-extX,  extY, -far); // upper left
		v5 = vect3( extX,  extY, -far); // upper right
		v6 = vect3( extX, -extY, -far); // lower right
		v7 = vect3(-extX, -extY, -far); // lower left
	}

	genBBox3((const VectorArrayImm< float, 3 >&) vtx, mBBox);

	{
		AttrBuffer16* vb = AttrBuffer16::s_default_register.insert(m_vtx);

		vb->setBufferType(AttrBuffer16::BUFFER_TYPE_VERTEX);

		static const AttrDescriptor scr[] =
		{
			AttrDescriptor(attr_semantics_position, ATTR_ELEMENT_FLOAT32, 3),
		};

		vb->setAttrScript(sizeof(scr) / sizeof(scr[0]), scr);	
		vb->setBulkTransfer(vtx);
	}

	if (0 == AttrBuffer16::s_default_register.seek(s_idx[0]))
	{
		static const uint16 __idx[8][3] =
		{
			{ 0, 1, 2 },
			{ 1, 2, 3 },
			{ 2, 3, 4 },
			{ 3, 4, 5 },
			{ 4, 5, 6 },
			{ 5, 6, 7 },
			{ 6, 7, 0 },
			{ 7, 0, 1 }
		};

		AttrBuffer16* ib = AttrBuffer16::s_default_register.insert(s_idx[0]);

		ib->setBufferType(AttrBuffer16::BUFFER_TYPE_INDEX);

		static const AttrDescriptor scr[] =
		{
			AttrDescriptor(attr_semantics_index, ATTR_ELEMENT_UINT16, 3)
		};

		VectorArrayImm< uint16, 3 > idx(__idx, sizeof(__idx) / sizeof(__idx[0]));

		ib->setAttrScript(sizeof(scr) / sizeof(scr[0]), scr);
		ib->setBulkCopy(idx);
	}

	if (0 == AttrBuffer16::s_default_register.seek(s_idx[1]))
	{
		static const uint16 __idx[12][2] =
		{
			{ 0, 1 },
			{ 0, 2 },
			{ 1, 3 },
			{ 2, 3 },
			{ 2, 4 },
			{ 3, 5 },
			{ 4, 5 },
			{ 4, 6 },
			{ 5, 7 },
			{ 6, 7 },
			{ 6, 0 },
			{ 7, 1 }
		};

		AttrBuffer16* ib = AttrBuffer16::s_default_register.insert(s_idx[1]);

		ib->setBufferType(AttrBuffer16::BUFFER_TYPE_INDEX);

		static const AttrDescriptor scr[] =
		{
			AttrDescriptor(attr_semantics_index, ATTR_ELEMENT_UINT16, 2)
		};

		VectorArrayImm< uint16, 2 > idx(__idx, sizeof(__idx) / sizeof(__idx[0]));

		ib->setAttrScript(sizeof(scr) / sizeof(scr[0]), scr);
		ib->setBulkCopy(idx);
	}
}


void CameraVisual::draw()
{
	GLuint prog = 0;
	glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*) &prog);

	if (!prog)
	{
		fprintf(stderr, "%s: no gpu program to draw with; bailing out\n", __FUNCTION__);
		return;
	}

	const AttrBuffer16* attr_buffer;

	if (0 == (attr_buffer = AttrBuffer16::s_default_register.seek(m_vtx)) ||
		attr_buffer->getBufferType() != AttrBuffer16::BUFFER_TYPE_VERTEX || !attr_buffer->actuate(m_vtx))
	{
		fprintf(stderr, "%s: failed actuating vertex buffer '%s'; bailing out\n", __FUNCTION__, m_vtx.c_str());
		return;
	}

	const AttrDescriptor& desc = attr_buffer->getAttrDescriptor(0);
	const unsigned offset = attr_buffer->getAttrOffset(0);

	const GLint attrLoc = glGetAttribLocation(prog, desc.semantics.c_str());

	if (-1 == attrLoc)
	{
		fprintf(stderr, "%s: missing attribute '%s' from gpu program; bailing out\n", __FUNCTION__, desc.semantics.c_str());
		return;
	}

	glVertexAttribPointer(attrLoc, desc.element_count, GL_FLOAT, GL_FALSE, attr_buffer->getBufferRecSize(), (const GLvoid *) offset);
	glEnableVertexAttribArray(attrLoc);

	const AttrBuffer16* attr_buffei;

	if (0 == (attr_buffei = AttrBuffer16::s_default_register.seek(s_idx[0])) ||
		attr_buffei->getBufferType() != AttrBuffer16::BUFFER_TYPE_INDEX || !attr_buffei->actuate(s_idx[0]))
	{
		fprintf(stderr, "%s: failed actuating index buffer '%s'; bailing out\n", __FUNCTION__, s_idx[0].c_str());
		goto clean_exit;
	}

	// draw camera-frustum's solid walls
	{
		const AttrDescriptor& desi = attr_buffei->getAttrDescriptor(0);

		glDrawElements(GL_TRIANGLE_STRIP, attr_buffei->getBufferLength() * desi.element_count, GL_UNSIGNED_SHORT, 0);
	}

	if (reportGLError(stderr))
		fprintf(stderr, "error: failed drawing elements with attribute array buffer '%s'\n", s_idx[0].c_str());

	if (0 == (attr_buffei = AttrBuffer16::s_default_register.seek(s_idx[1])) ||
		attr_buffei->getBufferType() != AttrBuffer16::BUFFER_TYPE_INDEX || !attr_buffei->actuate(s_idx[1]))
	{
		fprintf(stderr, "%s: failed actuating index buffer '%s'; bailing out\n", __FUNCTION__, s_idx[1].c_str());
		goto clean_exit;
	}

	// draw camera-frustum's wire cage
	{
		const AttrDescriptor& desi = attr_buffei->getAttrDescriptor(0);

		glDrawElements(GL_LINES, attr_buffei->getBufferLength() * desi.element_count, GL_UNSIGNED_SHORT, 0);
	}

	if (reportGLError(stderr))
		fprintf(stderr, "error: failed drawing elements with attribute array buffer '%s'\n", s_idx[1].c_str());

clean_exit:

	glDisableVertexAttribArray(attrLoc);
}


void CameraVisual::drawWire()
{
	draw();
}

} // namespace rend

